/*



<This Java Class is part of the jMusic API version 1.5, March 2004.>



Copyright (C) 2000 Andrew Sorensen & Andrew Brown



This program is free software; you can redistribute it and/or modify

it under the terms of the GNU General Public License as published by

the Free Software Foundation; either version 2 of the License, or any

later version.



This program is distributed in the hope that it will be useful, but

WITHOUT ANY WARRANTY; without even the implied warranty of

MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

GNU General Public License for more details.



You should have received a copy of the GNU General Public License

along with this program; if not, write to the Free Software

Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.



*/


package jm.audio.synth;


import jm.audio.AOException;
import jm.audio.AudioObject;
import jm.audio.Instrument;


/**
 * Wavetable lookup creates an efficient means for resampling data
 * <p/>
 * into any frequency. It is particularly useful for holding simple
 * <p/>
 * wave information such as sinewaves and is often used as an
 * <p/>
 * oscillator.<br>
 * <p/>
 * This WaveTable implementation can accept either one or two inputs.  Two
 * <p/>
 * inputs expects that amplitude is the left input and frequency is the right
 * <p/>
 * input. One input allows the user to specify whether the input is for
 * <p/>
 * amplitude or frequency by setting the aoDestination variable((0)amplitude
 * <p/>
 * (1)frequency.<br>
 * <p/>
 * A WaveTable can use fixed variables for both amplitude and frequency.  The
 * <p/>
 * default is to use a default amplitude of 1.0 and a frequency based upon the
 * <p/>
 * value of the build methods Note.pitch(). These fixed variables are
 * <p/>
 * amp(amplitude) and frq(frequency) and both have set methods. NOTE (please do
 * <p/>
 * not add more constructors to set amp and frq but leave them as set
 * <p/>
 * methods).<br>
 * <p/>
 * The frqRatio variable is used to produce a frequency which is a ratio
 * <p/>
 * against the current notesFrq (the build methods note.getFrequency()) variable
 * <p/>
 * setting.<br>
 * <p/>
 * It is common to use WaveTables as oscillators and even more common for these
 * <p/>
 * oscillators to based on simple wave forms.  Simple wave forms in jMusic can
 * <p/>
 * be retrieved using static method calls to the Oscillator class.<br>
 *
 * @author Andrew Sorensen
 * @version 1.0, Sun Feb 25 18:42:52  2001
 */

public class WaveTable extends AudioObject {

    //----------------------------------------------

    // Attributes

    //----------------------------------------------

    /**
     * constant for use with aoDestination
     */

    public static final int AMPLITUDE = 0;
    /**
     * constant for use with aoDestination
     */

    public static final int FREQUENCY = 1;
    /**
     * constant for use with channels
     */

    public static final int MONO = 1;
    /**
     * constant for use with channels
     */

    public static final int STEREO = 2;
    /**
     * this contains the wavetable data as samples
     */

    private float[] waveTable;
    /**
     * how many samples to we skip while passing through the wavetable
     */

    private float si;
    /**
     * what is the phase of the wavetable to start at
     */

    private float phase;
    /**
     * If we have one audio object input is at amp(0) or freq(1) ?
     */

    private int aoDestination;
    /**
     * Value to use as a fixed amplitude for the waveTable.
     */

    private float amp = (float) 1.0;
    /**
     * Value to use as a fixed frequency for the waveTable.
     */

    private float frq = (float) -1.0;
    /**
     * Frequency ratio allows an incoming note's pitch to be adjusted to a
     * <p/>
     * fixed ratio amount
     */

    private float frqRatio = (float) 1.0;

    //----------------------------------------------

    // Constructors

    //----------------------------------------------

    /**
     * This constructor sets the WaveTable to act as
     * <p/>
     * a processor object taking in two inputs. Input
     * <p/>
     * one is defined as amplitude and input two is
     * <p/>
     * defined as frequency.
     *
     * @param ao        AudioObject as input
     * @param waveTable the lookup table data
     * @throws AOException thrown when two many inputs are attached
     */

    public WaveTable(AudioObject[] ao, float[] waveTable) throws AOException {

        super(ao, "[WaveTable]");

        if (ao.length > 2) throw new AOException(this.name, 1);

        this.waveTable = waveTable;

    }


    /**
     * This constructor sets the WaveTable to act as
     * <p/>
     * a processor object taking in one input. That
     * <p/>
     * input can be either amplitude(0) or frequency(1)
     * <p/>
     * and is defined by the aoDestination variable (int).
     *
     * @param ao            the one input audio object
     * @param wavetable     the wave table data
     * @param aoDestination Is this input amplitude(0) or frequency(1)
     */

    public WaveTable(AudioObject ao, float[] waveTable, int aoDestination) {

        super(ao, "[WaveTable]");

        this.waveTable = waveTable;

        this.aoDestination = aoDestination;

    }


    /**
     * This constructor sets the WaveTable to act as
     * <p/>
     * a generator object taking in one input. That
     * <p/>
     * input can be either amplitude(0) or frequency(1)
     * <p/>
     * and is defined by the aoDestination variable (int).
     *
     * @param inst          the parent instrument (usually "this")
     * @param wavetable     the wave table data
     * @param aoDestination Is this input amplitude(0) or frequency(1)
     * @param val           is used to set a fixed frequency or amplitude based on the
     *                      <p/>
     *                      result of aoDestination (aoDestination=1 for example will set a fixed frequency)
     */

    public WaveTable(Instrument inst, int sampleRate, float[] waveTable,

                     int channels, int aoDestination, float val) {

        super(inst, sampleRate, "[WaveTable]");

        this.waveTable = waveTable;

        this.channels = channels;

        this.aoDestination = aoDestination;

        if (aoDestination == 1) {

            this.frq = val;

        } else {

            this.amp = val;

        }

    }


    /**
     * This constructor sets this wavetable up as a generator
     * <p/>
     * object meaning that it will pass sample information
     * <p/>
     * down the chain based on its wave table data.<br>
     * <p/>
     * Set WaveTable with some initial values including
     * <p/>
     * the sampling rate and the samples to use for this
     * <p/>
     * wave table
     *
     * @param inst       the parent instrument (usually "this")
     * @param sampleRate the sampling rate
     * @param waveTable  the wave table data
     * @param channels   the number of channels to use
     */

    public WaveTable(Instrument inst, int sampleRate, float[] waveTable,

                     int channels) {

        super(inst, sampleRate, "[WaveTable]");

        this.waveTable = waveTable;

        this.channels = channels;

    }


    //----------------------------------------------

    // Methods

    //----------------------------------------------

    /**
     * Moves through the WaveTable array (noramally forwards but sometimes
     * <p/>
     * backwards) by increments set by si (sample increment value).  This nextWork
     * <p/>
     * method can take one or two inputs which are either amplitude, frequency
     * <p/>
     * or both (a single input can be assigned to either frequency or amplitude
     * <p/>
     * by assigning the aoDestination value to either (0)Amp or (1)Frq in the
     * <p/>
     * appropriate constructor.  A WaveTable that takes two inputs expects the
     * <p/>
     * first input to be amplitude and the second input to be frequency.
     *
     * @param buffer The sample buffer.
     */

    public int work(float[] buffer) throws AOException {

        //because wavetable contains mono sample data we need to pass the same

        //sample information to as many channels as are present.

        int buffneed = buffer.length / channels;

        int ret = 0; //the number of samples to return


        if (inputs == 2) { //Amp and Freq

            float[] ampbuf = new float[buffneed];

            int returned = this.previous[0].nextWork(ampbuf);

            float[] freqbuf = new float[returned];

            if (returned != this.previous[1].work(freqbuf)) {

                throw new AOException(this.name, 0);

            }

            for (int i = 0; ret < buffer.length; i++) {

                setSI((int) freqbuf[i]);

                if (phase < 0) {

                    phase = this.waveTable.length + phase;

                } // amplitude values assumed to be between -1 and 1

                float sample = waveTable[(int) phase] * (this.amp * ampbuf[i]);

                this.phase += si;

                if (phase >= this.waveTable.length) {

                    phase -= this.waveTable.length;

                }

                for (int j = 0; j < channels; j++) {

                    buffer[ret++] = sample;

                }

            }

        } else if (inputs == 1 && aoDestination == 0) { //Amp only

            float[] ampbuf = new float[buffneed];

            int returned = this.previous[0].nextWork(ampbuf);

            for (int i = 0; ret < buffer.length; i++) {

                float sample = waveTable[(int) phase] * (this.amp * ampbuf[i]);

                this.phase += si;

                if (phase >= this.waveTable.length) {

                    phase -= this.waveTable.length;

                }

                for (int j = 0; j < channels; j++) {

                    buffer[ret++] = sample;

                }

            }

        } else if (inputs == 1 && aoDestination == 1) { //Frq only

            float[] frqbuf = new float[buffneed];

            int returned = this.previous[0].work(frqbuf);

            for (int i = 0; i < buffneed; i++) {

                setSI((int) frqbuf[i]);

                if (phase < 0) {

                    phase = this.waveTable.length + phase;

                }

                float sample = waveTable[(int) phase] * this.amp;

                this.phase += si;

                if (phase >= this.waveTable.length) {

                    phase -= this.waveTable.length;

                }

                for (int j = 0; j < channels; j++) {

                    buffer[ret++] = sample;

                }

            }

        } else { //no inputs

            for (; ret < buffer.length; ) {

                float sample = waveTable[(int) phase] * this.amp;

                this.phase += si;

                if (phase >= this.waveTable.length) {

                    phase -= this.waveTable.length;

                }

                for (int j = 0; j < channels; j++) {

                    try {

                        buffer[ret++] = sample;

                    } catch (ArrayIndexOutOfBoundsException e) {

                        //This can happen if a non mono signal chain wants

                        //to access the wavetable as a mono signal

                        //Ignore and skip over

                        //

                        //We do need to remove one back off ret though to return

                        //the right number of samples to return

                        ret--;

                    }

                }

            }

        }

        return ret;

    }


    /**

     */

    public void build() {

        float notesFrq = (float) currentNote.getFrequency() * frqRatio;

        if (this.frq < (float) 0.0) {

            this.setSI(notesFrq);

        } else {

            this.setSI(this.frq);

        }

    }


    /**
     * Set the fixed amp of this wavetable
     *
     * @param amp Fixed value amplitude
     */

    public void setAmp(float amp) {

        this.amp = amp;

    }


    /**
     * Set the fixed Frequecy of this wavetable
     *
     * @param frq Fixed value frequency
     */

    public void setFrq(float frq) {

        this.frq = frq;

    }


    /**
     * Sets the frequency ratio to alter a notes pitch by
     *
     * @param frqRatio Fixed ratio value to change frequency by
     */

    public void setFrqRatio(float frqRatio) {

        this.frqRatio = frqRatio;

    }


    //------------------------------------------

    // Protected Methods

    //------------------------------------------

    /**
     * Returns the sampling increment which is used
     * <p/>
     * to nextWork out how many samples in the wavetable
     * <p/>
     * skip on each pass.
     *
     * @param frequency the frequency used to find si
     */

    protected void setSI(float frequency) {

        this.si = (frequency / (float) this.sampleRate) * (float) this.waveTable.length;

    }

}

